Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / core / data / src / commonTest / kotlin / org / meshtastic / core / data / manager / MeshDataHandlerTest.kt

Displaying Raw • Download

core/data/src/commonTest/kotlin/org/meshtastic/core/data/manager/MeshDataHandlerTest.kt 01cd54907d62cd90913d3d071b6bc240cae365ef (01cd5490) Text, 43.75 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.data.manager

Tff7b72import T7ee787dev.mokkery.MockMode
Tff7b72import T7ee787dev.mokkery.answering.calls
Tff7b72import T7ee787dev.mokkery.answering.returns
Tff7b72import T7ee787dev.mokkery.every
Tff7b72import T7ee787dev.mokkery.everySuspend
Tff7b72import T7ee787dev.mokkery.matcher.any
Tff7b72import T7ee787dev.mokkery.mock
Tff7b72import T7ee787dev.mokkery.verify
Tff7b72import T7ee787dev.mokkery.verify.VerifyMode.Companion.exactly
Tff7b72import T7ee787dev.mokkery.verifySuspend
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.ExperimentalCoroutinesApi
Tff7b72import T7ee787kotlinx.coroutines.cancel
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.StateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.emptyFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOf
Tff7b72import T7ee787kotlinx.coroutines.test.StandardTestDispatcher
Tff7b72import T7ee787kotlinx.coroutines.test.TestScope
Tff7b72import T7ee787kotlinx.coroutines.test.advanceUntilIdle
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.model.ContactSettings
Tff7b72import T7ee787org.meshtastic.core.model.DataPacket
Tff7b72import T7ee787org.meshtastic.core.model.Node
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.core.model.util.MeshDataMapper
Tff7b72import T7ee787org.meshtastic.core.repository.AdminPacketHandler
Tff7b72import T7ee787org.meshtastic.core.repository.MeshBeaconPrefs
Tff7b72import T7ee787org.meshtastic.core.repository.MeshBeaconRepository
Tff7b72import T7ee787org.meshtastic.core.repository.MeshNotificationManager
Tff7b72import T7ee787org.meshtastic.core.repository.MessageFilter
Tff7b72import T7ee787org.meshtastic.core.repository.NeighborInfoHandler
Tff7b72import T7ee787org.meshtastic.core.repository.NodeManager
Tff7b72import T7ee787org.meshtastic.core.repository.NotificationManager
Tff7b72import T7ee787org.meshtastic.core.repository.PacketHandler
Tff7b72import T7ee787org.meshtastic.core.repository.PacketRepository
Tff7b72import T7ee787org.meshtastic.core.repository.PlatformAnalytics
Tff7b72import T7ee787org.meshtastic.core.repository.RadioConfigRepository
Tff7b72import T7ee787org.meshtastic.core.repository.RadioInterfaceService
Tff7b72import T7ee787org.meshtastic.core.repository.RadioSessionContext
Tff7b72import T7ee787org.meshtastic.core.repository.RadioSessionLease
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceRepository
Tff7b72import T7ee787org.meshtastic.core.repository.StoreForwardPacketHandler
Tff7b72import T7ee787org.meshtastic.core.repository.TelemetryPacketHandler
Tff7b72import T7ee787org.meshtastic.core.repository.TracerouteHandler
Tff7b72import T7ee787org.meshtastic.core.testing.FakeNotificationPrefs
Tff7b72import T7ee787org.meshtastic.proto.ChannelSet
Tff7b72import T7ee787org.meshtastic.proto.ChannelSettings
Tff7b72import T7ee787org.meshtastic.proto.Data
Tff7b72import T7ee787org.meshtastic.proto.MeshBeacon
Tff7b72import T7ee787org.meshtastic.proto.MeshPacket
Tff7b72import T7ee787org.meshtastic.proto.NeighborInfo
Tff7b72import T7ee787org.meshtastic.proto.Paxcount
Tff7b72import T7ee787org.meshtastic.proto.PortNum
Tff7b72import T7ee787org.meshtastic.proto.Position
Tff7b72import T7ee787org.meshtastic.proto.Routing
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787org.meshtastic.proto.User
Tff7b72import T7ee787org.meshtastic.proto.Waypoint
Tff7b72import T7ee787kotlin.test.AfterTest
Tff7b72import T7ee787kotlin.test.BeforeTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertNotNull

Tf0883e@OptInTb4b4b4(Te6edf3ExperimentalCoroutinesApiTff7b72::Te6edf3classTb4b4b4)
Tff7b72class T56d364MeshDataHandlerTest Tb4b4b4{

Tff7b72private Tff7b72lateinit Tff7b72var Te6edf3handlerTb4b4b4: Te6edf3MeshDataHandlerImpl
Tff7b72private Tff7b72val Te6edf3nodeManagerTb4b4b4: Te6edf3NodeManager Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3packetHandlerTb4b4b4: Te6edf3PacketHandler Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3serviceRepositoryTb4b4b4: Te6edf3ServiceRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3packetRepositoryTb4b4b4: Te6edf3PacketRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3notificationManagerTb4b4b4: Te6edf3NotificationManager Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3serviceNotificationsTb4b4b4: Te6edf3MeshNotificationManager Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3analyticsTb4b4b4: Te6edf3PlatformAnalytics Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3dataMapperTb4b4b4: Te6edf3MeshDataMapper Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3tracerouteHandlerTb4b4b4: Te6edf3TracerouteHandler Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3neighborInfoHandlerTb4b4b4: Te6edf3NeighborInfoHandler Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3radioConfigRepositoryTb4b4b4: Te6edf3RadioConfigRepository Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3messageFilterTb4b4b4: Te6edf3MessageFilter Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3storeForwardHandlerTb4b4b4: Te6edf3StoreForwardPacketHandler Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3telemetryHandlerTb4b4b4: Te6edf3TelemetryPacketHandler Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3adminPacketHandlerTb4b4b4: Te6edf3AdminPacketHandler Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3radioInterfaceServiceTb4b4b4: Te6edf3RadioInterfaceService Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)
Tff7b72private Tff7b72val Te6edf3session Tff7b72= Te6edf3RadioSessionContextTb4b4b4(Te6edf3generation Tff7b72= T79c0ff7LTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6fftcp:testTa5d6ff"Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8fftestLeaseTb4b4b4(Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContextTb4b4b4)Tb4b4b4: Te6edf3RadioSessionLease Tff7b72= Tff7b72object Tb4b4b4: T56d364RadioSessionLease Tb4b4b4{
Tff7b72override Tff7b72val Te6edf3sessionTb4b4b4: Te6edf3RadioSessionContext Tff7b72= Te6edf3session

Tff7b72override Tff7b72fun Td2a8ffisCurrentTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Tff7b72true
Tb4b4b4}

Tff7b72private Tff7b72fun Te6edf3MeshDataHandlerImplTb4b4b4.Td2a8ffhandleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3MeshPacketTb4b4b4, Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4, Te6edf3sessionTb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72val Te6edf3testDispatcher Tff7b72= Te6edf3StandardTestDispatcherTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3testScope Tff7b72= Te6edf3TestScopeTb4b4b4(Te6edf3testDispatcherTb4b4b4)

T8b949e// Separate scope for the real GeofenceMonitor: its serial worker is a never-completing coroutine, so it must NOT
T8b949e// live in the runTest scope (that would trip UncompletedCoroutinesError). Shares the dispatcher so advanceUntilIdle
T8b949e// still drives it; cancelled in tearDown.
Tff7b72private Tff7b72val Te6edf3geofenceScope Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3testDispatcherTb4b4b4)

T8b949e// Real repository over an in-memory prefs fake — its persistence write-through is exercised without a DataStore.
Tff7b72private Tff7b72val Te6edf3fakeBeaconPrefs Tff7b72=
Tff7b72object Tb4b4b4: T56d364MeshBeaconPrefs Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3flow Tff7b72= Te6edf3MutableStateFlowTff7b72<Te6edf3ListTff7b72<Tffa657StringTff7b72>Tff7b72>Tb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72override Tff7b72val Te6edf3storedBeaconsTb4b4b4: Te6edf3StateFlowTff7b72<Te6edf3ListTff7b72<Tffa657StringTff7b72>Tff7b72> Tff7b72= Te6edf3flow

Tff7b72override Tff7b72fun Td2a8ffsetStoredBeaconsTb4b4b4(Te6edf3recordsTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4) Tb4b4b4{
Te6edf3flowTb4b4b4.Te6edf3value Tff7b72= Te6edf3records
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3meshBeaconRepository Tff7b72= Te6edf3MeshBeaconRepositoryTb4b4b4(Te6edf3fakeBeaconPrefsTb4b4b4, Te6edf3geofenceScopeTb4b4b4)

Tf0883e@AfterTest
Tff7b72fun Td2a8fftearDownTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3geofenceScopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tf0883e@BeforeTest
Tff7b72fun Td2a8ffsetUpTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3handler Tff7b72=
Te6edf3MeshDataHandlerImplTb4b4b4(
Te6edf3nodeManager Tff7b72= Te6edf3nodeManagerTb4b4b4,
Te6edf3packetHandler Tff7b72= Te6edf3packetHandlerTb4b4b4,
Te6edf3serviceStateWriter Tff7b72= Te6edf3serviceRepositoryTb4b4b4,
Te6edf3packetRepository Tff7b72= Te6edf3lazy Tb4b4b4{ Te6edf3packetRepository Tb4b4b4}Tb4b4b4,
Te6edf3notificationManager Tff7b72= Te6edf3notificationManagerTb4b4b4,
Te6edf3serviceNotifications Tff7b72= Te6edf3serviceNotificationsTb4b4b4,
Te6edf3analytics Tff7b72= Te6edf3analyticsTb4b4b4,
Te6edf3dataMapper Tff7b72= Te6edf3dataMapperTb4b4b4,
Te6edf3tracerouteHandler Tff7b72= Te6edf3tracerouteHandlerTb4b4b4,
Te6edf3neighborInfoHandler Tff7b72= Te6edf3neighborInfoHandlerTb4b4b4,
Te6edf3radioConfigRepository Tff7b72= Te6edf3radioConfigRepositoryTb4b4b4,
Te6edf3messageFilter Tff7b72= Te6edf3messageFilterTb4b4b4,
Te6edf3storeForwardHandler Tff7b72= Te6edf3storeForwardHandlerTb4b4b4,
Te6edf3telemetryHandler Tff7b72= Te6edf3telemetryHandlerTb4b4b4,
Te6edf3adminPacketHandler Tff7b72= Te6edf3adminPacketHandlerTb4b4b4,
Te6edf3collectorRegistry Tff7b72= Te6edf3mockTb4b4b4(Te6edf3MockModeTb4b4b4.Te6edf3autofillTb4b4b4)Tb4b4b4,
T8b949e// GeofenceMonitor is a final @Single (mokkery can't mock it) — use a real one over mocked
T8b949e// collaborators. With no geofence-bearing waypoints emitted, onPositionReceived is a no-op.
Te6edf3geofenceMonitor Tff7b72=
Te6edf3GeofenceMonitorTb4b4b4(
Te6edf3packetRepository Tff7b72= Te6edf3lazy Tb4b4b4{ Te6edf3packetRepository Tb4b4b4}Tb4b4b4,
Te6edf3nodeManager Tff7b72= Te6edf3nodeManagerTb4b4b4,
Te6edf3serviceNotifications Tff7b72= Te6edf3serviceNotificationsTb4b4b4,
Te6edf3crossingStore Tff7b72= Te6edf3GeofenceCrossingStoreTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3notificationPrefs Tff7b72= Te6edf3FakeNotificationPrefsTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3radioInterfaceService Tff7b72= Te6edf3radioInterfaceServiceTb4b4b4,
Te6edf3scope Tff7b72= Te6edf3geofenceScopeTb4b4b4,
Tb4b4b4)Tb4b4b4,
Te6edf3meshBeaconRepository Tff7b72= Te6edf3meshBeaconRepositoryTb4b4b4,
Te6edf3radioInterfaceService Tff7b72= Te6edf3radioInterfaceServiceTb4b4b4,
Te6edf3scope Tff7b72= Te6edf3testScopeTb4b4b4,
Tb4b4b4)

Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWithSessionLeaseTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{
Tff7b72val Te6edf3requestedSession Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff0Tff7b72] Tff7b72as Te6edf3RadioSessionContext

Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUNCHECKED_CASTTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3block Tff7b72= Tffa657itTb4b4b4.Te6edf3argsTff7b72[T79c0ff1Tff7b72] Tff7b72as Tb4b4b4(Te6edf3suspend Tb4b4b4(Te6edf3RadioSessionLeaseTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4)
Te6edf3blockTb4b4b4(Te6edf3testLeaseTb4b4b4(Te6edf3requestedSessionTb4b4b4)Tb4b4b4)
Tff7b72true
Tb4b4b4}
T8b949e// Default: mapper returns null for empty packets, which is the safe default
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72null
T8b949e// Stub commonly accessed properties to avoid NPE from autofill
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72null
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNum Tb4b4b4} Te6edf3returns Te6edf3emptyMapTb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3radioConfigRepositoryTb4b4b4.Te6edf3channelSetFlow Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3ChannelSetTb4b4b4(Tb4b4b4)Tb4b4b4)
T8b949e// GeofenceMonitor collects this on init; stub it so the launched collector doesn't NPE on the test scope.
Te6edf3every Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getWaypointsTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyFlowTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8fftestInitializationTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3assertNotNullTb4b4b4(Te6edf3handlerTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedData returns early when dataMapper returns null`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3MeshPacketTb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72null

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

T8b949e// Should not broadcast if dataMapper returns null
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedData does not broadcast for position from local node`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3
Tff7b72val Te6edf3position Tff7b72= Te6edf3PositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3positionTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3myNodeNumTb4b4b4)Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3positionTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4.Te6edf3valueTb4b4b4,
Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)

T8b949e// Position from local node — no further action expected
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedData broadcasts for remote packets`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3
Tff7b72val Te6edf3remoteNum Tff7b72= T79c0ff4T79c0ff5T79c0ff6
Tff7b72val Te6edf3packet Tff7b72= Te6edf3MeshPacketTb4b4b4(Te6edf3from Tff7b72= Te6edf3remoteNumTb4b4b4, Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PRIVATE_APPTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3remoteNumTb4b4b4)Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PRIVATE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`handleReceivedData tracks analytics`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3MeshPacketTb4b4b4(Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PRIVATE_APPTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!otherTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Tff7b72nullTb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PRIVATE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3analyticsTb4b4b4.Te6edf3trackTb4b4b4(Ta5d6ff"Ta5d6ffnum_data_receiveTa5d6ff"Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Position handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`position packet delegates to nodeManager`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3
Tff7b72val Te6edf3remoteNum Tff7b72= T79c0ff4T79c0ff5T79c0ff6
Tff7b72val Te6edf3position Tff7b72= Te6edf3PositionTb4b4b4(Te6edf3latitude_i Tff7b72= T79c0ff4T79c0ff5T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4, Te6edf3longitude_i Tff7b72= T79c0ff9T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3remoteNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3positionTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3positionTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4.Te6edf3valueTb4b4b4,
Te6edf3time Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPositionTb4b4b4(Te6edf3remoteNumTb4b4b4, Te6edf3myNodeNumTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff0T79c0ff0T79c0ff0LTb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- NodeInfo handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`nodeinfo packet from remote delegates to handleReceivedUser`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3
Tff7b72val Te6edf3remoteNum Tff7b72= T79c0ff4T79c0ff5T79c0ff6
Tff7b72val Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemoteTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3remoteNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NODEINFO_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3userTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3userTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NODEINFO_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3remoteNumTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`nodeinfo packet from local node is ignored`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3
Tff7b72val Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffLocalTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffLTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NODEINFO_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3userTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3userTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NODEINFO_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)

Te6edf3verifyTb4b4b4(Te6edf3mode Tff7b72= Te6edf3devTb4b4b4.Te6edf3mokkeryTb4b4b4.Te6edf3verifyTb4b4b4.Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{
Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedUserTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// --- Paxcounter handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`paxcounter packet delegates to nodeManager`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3remoteNum Tff7b72= T79c0ff4T79c0ff5T79c0ff6
Tff7b72val Te6edf3pax Tff7b72= Te6edf3PaxcountTb4b4b4(Te6edf3wifi Tff7b72= T79c0ff1T79c0ff0Tb4b4b4, Te6edf3ble Tff7b72= T79c0ff5Tb4b4b4, Te6edf3uptime Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3remoteNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PAXCOUNTER_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3paxTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3paxTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3PAXCOUNTER_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3handleReceivedPaxcounterTb4b4b4(Te6edf3remoteNumTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Traceroute handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`traceroute packet delegates to tracerouteHandler and suppresses broadcast`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TRACEROUTE_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3byteArrayOfTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3byteArrayOfTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TRACEROUTE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3tracerouteHandlerTb4b4b4.Te6edf3handleTracerouteTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- NeighborInfo handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`neighborinfo packet delegates to neighborInfoHandler and broadcasts`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3ni Tff7b72= Te6edf3NeighborInfoTb4b4b4(Te6edf3node_id Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NEIGHBORINFO_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3niTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3niTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3NEIGHBORINFO_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3neighborInfoHandlerTb4b4b4.Te6edf3handleNeighborInfoTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Mesh Beacon handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`mesh beacon with a join offer is recorded as an invitation`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3beacon Tff7b72= Te6edf3MeshBeaconTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffJoin usTa5d6ff"Tb4b4b4, Te6edf3offer_channel Tff7b72= Te6edf3ChannelSettingsTb4b4b4(Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffPartyNetTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3MESH_BEACON_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3beaconTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3beaconTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3MESH_BEACON_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Tff7b72val Te6edf3offers Tff7b72= Te6edf3meshBeaconRepositoryTb4b4b4.Te6edf3offersTb4b4b4.Te6edf3value
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3offersTb4b4b4.Te6edf3sizeTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffJoin usTa5d6ff"Tb4b4b4, Te6edf3offersTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3messageTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6ffPartyNetTa5d6ff"Tb4b4b4, Te6edf3offersTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3channelNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`mesh beacon without a join offer is ignored`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3beacon Tff7b72= Te6edf3MeshBeaconTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffJust saying hiTa5d6ff"Tb4b4b4) T8b949e// no offer_channel → not actionable
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3MESH_BEACON_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3beaconTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3beaconTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3MESH_BEACON_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3meshBeaconRepositoryTb4b4b4.Te6edf3offersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`our own mesh beacon is ignored`Tb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// Spec FR-001: ignore beacons from the scanning node itself (else a listen+broadcast node self-notifies).
Tff7b72val Te6edf3beacon Tff7b72= Te6edf3MeshBeaconTb4b4b4(Te6edf3message Tff7b72= Ta5d6ff"Ta5d6ffJoin usTa5d6ff"Tb4b4b4, Te6edf3offer_channel Tff7b72= Te6edf3ChannelSettingsTb4b4b4(Te6edf3name Tff7b72= Ta5d6ff"Ta5d6ffPartyNetTa5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, T8b949e// == myNodeNum below
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3MESH_BEACON_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3beaconTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3DataPacketTb4b4b4(Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!selfTa5d6ff"Tb4b4b4, Te6edf3bytes Tff7b72= Te6edf3beaconTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3MESH_BEACON_APPTb4b4b4.Te6edf3valueTb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3assertEqualsTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3meshBeaconRepositoryTb4b4b4.Te6edf3offersTb4b4b4.Te6edf3valueTb4b4b4.Te6edf3sizeTb4b4b4)
Tb4b4b4}

T8b949e// --- Store-and-Forward handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`store forward packet delegates to storeForwardHandler`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3STORE_FORWARD_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3byteArrayOfTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3byteArrayOfTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3STORE_FORWARD_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3storeForwardHandlerTb4b4b4.Te6edf3handleStoreAndForwardTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Routing/ACK-NAK handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`routing packet with successful ack broadcasts and removes response`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3routing Tff7b72= Te6edf3RoutingTb4b4b4(Te6edf3error_reason Tff7b72= Te6edf3RoutingTb4b4b4.Te6edf3ErrorTb4b4b4.Te6edf3NONETb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ROUTING_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3routingTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3request_id Tff7b72= T79c0ff9T79c0ff9Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3routingTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ROUTING_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(T79c0ff4T79c0ff5T79c0ff6Tb4b4b4) Tb4b4b4} Te6edf3returns Ta5d6ff"Ta5d6ff!remoteTa5d6ff"

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetHandlerTb4b4b4.Te6edf3removeResponseTb4b4b4(T79c0ff9T79c0ff9Tb4b4b4, Te6edf3complete Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`routing ack from a retired generation cannot update the replacement database`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3routing Tff7b72= Te6edf3RoutingTb4b4b4(Te6edf3error_reason Tff7b72= Te6edf3RoutingTb4b4b4.Te6edf3ErrorTb4b4b4.Te6edf3NONETb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ROUTING_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3routingTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3request_id Tff7b72= T79c0ff9T79c0ff9Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3routingTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ROUTING_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(T79c0ff4T79c0ff5T79c0ff6Tb4b4b4) Tb4b4b4} Te6edf3returns Ta5d6ff"Ta5d6ff!remoteTa5d6ff"
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWithSessionLeaseTb4b4b4(Te6edf3sessionTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getPacketByPacketIdTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3updateTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetHandlerTb4b4b4.Te6edf3removeResponseTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`routing packet always broadcasts`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3routing Tff7b72= Te6edf3RoutingTb4b4b4(Te6edf3error_reason Tff7b72= Te6edf3RoutingTb4b4b4.Te6edf3ErrorTb4b4b4.Te6edf3NONETb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ROUTING_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3routingTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3request_id Tff7b72= T79c0ff9T79c0ff9Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3routingTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ROUTING_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3toNodeIDTb4b4b4(T79c0ff4T79c0ff5T79c0ff6Tb4b4b4) Tb4b4b4} Te6edf3returns Ta5d6ff"Ta5d6ff!remoteTa5d6ff"

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Tb4b4b4}

T8b949e// --- Telemetry handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`telemetry packet delegates to telemetryHandler`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3telemetry Tff7b72=
Te6edf3TelemetryTb4b4b4(
Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3device_metrics Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff8T79c0ff0Tb4b4b4, Te6edf3voltage Tff7b72= T79c0ff4.0fTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3telemetryTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3telemetryTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4.Te6edf3valueTb4b4b4,
Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3telemetryHandlerTb4b4b4.Te6edf3handleTelemetryTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`telemetry from local node delegates to telemetryHandler`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3myNodeNum Tff7b72= T79c0ff1T79c0ff2T79c0ff3
Tff7b72val Te6edf3telemetry Tff7b72=
Te6edf3TelemetryTb4b4b4(
Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3device_metrics Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3DeviceMetricsTb4b4b4(Te6edf3battery_level Tff7b72= T79c0ff8T79c0ff0Tb4b4b4, Te6edf3voltage Tff7b72= T79c0ff4.0fTb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3from Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3telemetryTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3telemetryTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4.Te6edf3valueTb4b4b4,
Te6edf3time Tff7b72= T79c0ff2T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0LTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3myNodeNumTb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3telemetryHandlerTb4b4b4.Te6edf3handleTelemetryTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3myNodeNumTb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Text message handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`text message is persisted via rememberDataPacket`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3ContactSettingsTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3messageFilterTb4b4b4.Te6edf3shouldFilterTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
T8b949e// Provide sender node so getSenderName() doesn't fall back to getString (requires Skiko)
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemote UserTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRUTa5d6ff"Tb4b4b4)Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`text persistence from a retired session is rejected before database access`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff4T79c0ff5Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6fflateTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff4T79c0ff5Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Ta5d6ff"Ta5d6fflateTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3everySuspend Tb4b4b4{ Te6edf3radioInterfaceServiceTb4b4b4.Te6edf3runWithSessionLeaseTb4b4b4(Te6edf3sessionTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`duplicate text message is not inserted again`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
T8b949e// Return existing packet on duplicate check
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3listOfTb4b4b4(Te6edf3dataPacketTb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3devTb4b4b4.Te6edf3mokkeryTb4b4b4.Te6edf3verifyTb4b4b4.Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{
Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// --- Reaction handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`text with reply_id and emoji is treated as reaction`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3emojiBytes Tff7b72= Ta5d6ff"Ta5d6ff👍Ta5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3to Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(
Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4,
Te6edf3payload Tff7b72= Te6edf3emojiBytesTb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3reply_id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4,
Te6edf3emoji Tff7b72= T79c0ff1Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff9T79c0ff9Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3emojiBytesTb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3nodeDBbyNodeNum Tb4b4b4} Te6edf3returns
Te6edf3mapOfTb4b4b4(
T79c0ff4T79c0ff5T79c0ff6 Te6edf3to Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4,
T79c0ff1T79c0ff2T79c0ff3 Te6edf3to Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findReactionsWithIdTb4b4b4(T79c0ff9T79c0ff9Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3myNodeNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getPacketByPacketIdTb4b4b4(T79c0ff4T79c0ff2Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72null

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertReactionTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Range test / detection sensor handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`range test packet is remembered as text message type`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff5T79c0ff5Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3RANGE_TEST_APPTb4b4b4, Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff5T79c0ff5Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3RANGE_TEST_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(T79c0ff5T79c0ff5Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3ContactSettingsTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3messageFilterTb4b4b4.Te6edf3shouldFilterTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemote UserTa5d6ff"Tb4b4b4, Te6edf3short_name Tff7b72= Ta5d6ff"Ta5d6ffRUTa5d6ff"Tb4b4b4)Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

T8b949e// Range test should be remembered with TEXT_MESSAGE_APP dataType
Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Admin message handling ---

Tf0883e@Test
Tff7b72fun Td2a8ff`admin message delegates to adminPacketHandler`Tb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3admin Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3AdminMessageTb4b4b4(Te6edf3session_passkey Tff7b72= Te6edf3okioTb4b4b4.Te6edf3ByteStringTb4b4b4.Te6edf3ofTb4b4b4(T79c0ff1Tb4b4b4, T79c0ff2Tb4b4b4, T79c0ff3Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(Te6edf3from Tff7b72= T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ADMIN_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3adminTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!localTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3adminTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ADMIN_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)

Te6edf3verify Tb4b4b4{ Te6edf3adminPacketHandlerTb4b4b4.Te6edf3handleAdminMessageTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3sessionTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Message filtering ---

Tf0883e@Test
Tff7b72fun Td2a8ff`filtered message is inserted with filtered flag`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff7T79c0ff7Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(
Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4,
Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6ffspam contentTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff7T79c0ff7Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Ta5d6ff"Ta5d6ffspam contentTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(T79c0ff7T79c0ff7Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72null
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3ContactSettingsTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3messageFilterTb4b4b4.Te6edf3shouldFilterTb4b4b4(Ta5d6ff"Ta5d6ffspam contentTa5d6ff"Tb4b4b4, Tff7b72falseTb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72true

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

T8b949e// Verify insert was called with filtered = true (6th param)
Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3filtered Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`message from ignored node is filtered`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff8T79c0ff8Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff8T79c0ff8Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Ta5d6ff"Ta5d6ffhelloTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(T79c0ff8T79c0ff8Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3isIgnored Tff7b72= Tff7b72trueTb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3ContactSettingsTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3filtered Tff7b72= Tff7b72trueTb4b4b4) Tb4b4b4}
Tb4b4b4}

T8b949e// --- Mention / mute interaction (meshtastic/design#21) ---

Tff7b72private Tff7b72val Te6edf3myId Tff7b72= Ta5d6ff"Ta5d6ff!abcd1234Ta5d6ff"

Tff7b72private Tff7b72fun Td2a8ffmentionPacketTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1T79c0ff0T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6ffhey @Tffd700$Te6edf3myIdTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Tff7b72private Tff7b72fun Td2a8ffmentionDataPacketTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1T79c0ff0T79c0ff1Tb4b4b4,
Te6edf3from Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Ta5d6ff"Ta5d6ffhey @Tffd700$Te6edf3myIdTa5d6ff"Tb4b4b4.Te6edf3encodeToByteArrayTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)

Tf0883e@Test
Tff7b72fun Td2a8ff`mention from a muted node does not notify`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3mentionPacketTb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3mentionDataPacketTb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(T79c0ff1T79c0ff0T79c0ff1Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3ContactSettingsTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3messageFilterTb4b4b4.Te6edf3shouldFilterTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getMyIdTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3myId
T8b949e// Node mute is authoritative: a mention must NOT break through it.
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemote UserTa5d6ff"Tb4b4b4)Tb4b4b4, Te6edf3isMuted Tff7b72= Tff7b72trueTb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspendTb4b4b4(Te6edf3mode Tff7b72= Te6edf3devTb4b4b4.Te6edf3mokkeryTb4b4b4.Te6edf3verifyTb4b4b4.Te6edf3VerifyModeTb4b4b4.Te6edf3notTb4b4b4) Tb4b4b4{
Te6edf3serviceNotificationsTb4b4b4.Te6edf3updateMessageNotificationTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`mention from an unmuted node in a muted channel still notifies`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3mentionPacketTb4b4b4(Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3mentionDataPacketTb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(T79c0ff1T79c0ff0T79c0ff1Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
T8b949e// Channel/conversation muted — a mention breaks through it (design#21).
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3ContactSettingsTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4, Te6edf3isMuted Tff7b72= Tff7b72trueTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3messageFilterTb4b4b4.Te6edf3shouldFilterTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getMyIdTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3myId
Te6edf3every Tb4b4b4{ Te6edf3nodeManagerTb4b4b4.Te6edf3getNodeByIdTb4b4b4(Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4) Tb4b4b4} Te6edf3returns
Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff4T79c0ff5T79c0ff6Tb4b4b4, Te6edf3user Tff7b72= Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!remoteTa5d6ff"Tb4b4b4, Te6edf3long_name Tff7b72= Ta5d6ff"Ta5d6ffRemote UserTa5d6ff"Tb4b4b4)Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{
Te6edf3serviceNotificationsTb4b4b4.Te6edf3updateMessageNotificationTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3isSilent Tff7b72= Tff7b72falseTb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// --- Waypoint persisted-owner enforcement ---
T8b949e//
T8b949e// A locked waypoint (locked_to != 0) may only be modified by the node it is locked to. The inbound-payload check
T8b949e// alone (locked_to == from) cannot enforce this: a non-owner can still replay an existing id with locked_to = 0
T8b949e// (unlock) or their own num (takeover). handleWaypoint additionally consults the currently-stored owner.

Tff7b72private Tff7b72fun Td2a8ffwaypointPacketTb4b4b4(Te6edf3txIdTb4b4b4: Tffa657IntTb4b4b4, Te6edf3fromTb4b4b4: Tffa657IntTb4b4b4, Te6edf3waypointTb4b4b4: Te6edf3WaypointTb4b4b4)Tb4b4b4: Te6edf3MeshPacket Tb4b4b4{
Tff7b72val Te6edf3payload Tff7b72= Te6edf3waypointTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(Te6edf3id Tff7b72= Te6edf3txIdTb4b4b4, Te6edf3from Tff7b72= Te6edf3fromTb4b4b4, Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3WAYPOINT_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3payloadTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3dataPacket Tff7b72=
Te6edf3DataPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3txIdTb4b4b4,
Te6edf3from Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3fromTb4b4b4)Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4,
Te6edf3bytes Tff7b72= Te6edf3payloadTb4b4b4,
Te6edf3dataType Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3WAYPOINT_APPTb4b4b4.Te6edf3valueTb4b4b4,
Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3dataMapperTb4b4b4.Te6edf3toDataPacketTb4b4b4(Te6edf3packetTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3dataPacket
Tff7b72return Te6edf3packet
Tb4b4b4}

T8b949e/** Persist a single stored waypoint (via the getWaypoints firehose) so handleWaypoint can read its owner. */
Tff7b72private Tff7b72fun Td2a8ffstoreWaypointTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4, Te6edf3lockedToTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3stored Tff7b72=
Te6edf3DataPacketTb4b4b4(Te6edf3to Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCASTTb4b4b4, Te6edf3channel Tff7b72= T79c0ff0Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= Te6edf3idTb4b4b4, Te6edf3locked_to Tff7b72= Te6edf3lockedToTb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getWaypointsTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3storedTb4b4b4)Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffstubWaypointPersistDependenciesTb4b4b4(Te6edf3txIdTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3findPacketsWithIdTb4b4b4(Te6edf3txIdTb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3emptyListTb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getContactSettingsTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3ContactSettingsTb4b4b4(Te6edf3contactKey Tff7b72= Ta5d6ff"Ta5d6fftestTa5d6ff"Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3messageFilterTb4b4b4.Te6edf3shouldFilterTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Tff7b72false
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`non-owner unlock replay of a locked waypoint is dropped`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Te6edf3storeWaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3lockedTo Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4)
T8b949e// Mallory (999) replays waypoint 42 with locked_to = 0 (unlock). The inbound check passes, so only the
T8b949e// stored-owner check can drop it.
Tff7b72val Te6edf3packet Tff7b72= Te6edf3waypointPacketTb4b4b4(Te6edf3txId Tff7b72= T79c0ff5T79c0ff0T79c0ff0Tb4b4b4, Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3locked_to Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3stubWaypointPersistDependenciesTb4b4b4(T79c0ff5T79c0ff0T79c0ff0Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`non-owner takeover of a locked waypoint is dropped`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Te6edf3storeWaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3lockedTo Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4)
T8b949e// Mallory (999) locks waypoint 42 to herself. The inbound check passes (locked_to == from), so only the
T8b949e// stored-owner check can catch this.
Tff7b72val Te6edf3packet Tff7b72= Te6edf3waypointPacketTb4b4b4(Te6edf3txId Tff7b72= T79c0ff5T79c0ff0T79c0ff1Tb4b4b4, Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3locked_to Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4)Tb4b4b4)
Te6edf3stubWaypointPersistDependenciesTb4b4b4(T79c0ff5T79c0ff0T79c0ff1Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`owner unlock of their own locked waypoint is accepted`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Te6edf3storeWaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3lockedTo Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72= Te6edf3waypointPacketTb4b4b4(Te6edf3txId Tff7b72= T79c0ff5T79c0ff0T79c0ff2Tb4b4b4, Te6edf3from Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3locked_to Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3stubWaypointPersistDependenciesTb4b4b4(T79c0ff5T79c0ff0T79c0ff2Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`owner edit of their own locked waypoint is accepted`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Te6edf3storeWaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3lockedTo Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72= Te6edf3waypointPacketTb4b4b4(Te6edf3txId Tff7b72= T79c0ff5T79c0ff0T79c0ff3Tb4b4b4, Te6edf3from Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3locked_to Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4)Tb4b4b4)
Te6edf3stubWaypointPersistDependenciesTb4b4b4(T79c0ff5T79c0ff0T79c0ff3Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`new waypoint from a non-owner is accepted when none is stored`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
T8b949e// Nothing persisted yet: getWaypoints() emits an empty list (as Room does). A creation, not a hijack.
Te6edf3every Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3getWaypointsTb4b4b4(Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(Te6edf3emptyListTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72= Te6edf3waypointPacketTb4b4b4(Te6edf3txId Tff7b72= T79c0ff5T79c0ff0T79c0ff4Tb4b4b4, Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3locked_to Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3stubWaypointPersistDependenciesTb4b4b4(T79c0ff5T79c0ff0T79c0ff4Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`non-owner update to an unlocked waypoint is accepted`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
Te6edf3storeWaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3lockedTo Tff7b72= T79c0ff0Tb4b4b4)
Tff7b72val Te6edf3packet Tff7b72= Te6edf3waypointPacketTb4b4b4(Te6edf3txId Tff7b72= T79c0ff5T79c0ff0T79c0ff5Tb4b4b4, Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3locked_to Tff7b72= T79c0ff0Tb4b4b4)Tb4b4b4)
Te6edf3stubWaypointPersistDependenciesTb4b4b4(T79c0ff5T79c0ff0T79c0ff5Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`waypoint locked to someone other than the sender is dropped`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3testScopeTb4b4b4.Te6edf3runTest Tb4b4b4{
T8b949e// Pre-existing inbound-payload rule: a node can only lock a waypoint to itself. Nothing stored here — the
T8b949e// payload itself is invalid, so it is rejected before any repository read.
Tff7b72val Te6edf3packet Tff7b72= Te6edf3waypointPacketTb4b4b4(Te6edf3txId Tff7b72= T79c0ff5T79c0ff0T79c0ff6Tb4b4b4, Te6edf3from Tff7b72= T79c0ff9T79c0ff9T79c0ff9Tb4b4b4, Te6edf3waypoint Tff7b72= Te6edf3WaypointTb4b4b4(Te6edf3id Tff7b72= T79c0ff4T79c0ff2Tb4b4b4, Te6edf3locked_to Tff7b72= T79c0ff1T79c0ff1T79c0ff1Tb4b4b4)Tb4b4b4)
Te6edf3stubWaypointPersistDependenciesTb4b4b4(T79c0ff5T79c0ff0T79c0ff6Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3handleReceivedDataTb4b4b4(Te6edf3packetTb4b4b4, T79c0ff1T79c0ff2T79c0ff3Tb4b4b4)
Te6edf3advanceUntilIdleTb4b4b4(Tb4b4b4)

Te6edf3verifySuspendTb4b4b4(Te6edf3exactlyTb4b4b4(T79c0ff0Tb4b4b4)Tb4b4b4) Tb4b4b4{ Te6edf3packetRepositoryTb4b4b4.Te6edf3insertTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.28s